<!--
	签名组件
    案例：添加标签<qm-sign ref="signSheet"></qm-sign>
    调用签名：this.$refs.signSheet.openFaceSign(option);
    当签名点击确定后将会调用saveSignCallback方法

		props
			title[string => ''] 标题
			resignClick[Function => undefined] 点击“重签”时，需要调用的方法。必选，必选，必选
			saveSignCallback[Function => undefined] 点击“确定”且我签名成功时，需要调用的方法。必选，必选，必选
			saveFaceSignCallback[Function => undefined] 人脸签名成功时，调用的方法

    通过 refs 访问或调用数据请查看data()、methods
-->
<template>
  <f7-sheet
    class="sign-popup"
    style="height: 100%; background: #cfd5da"
    @sheet:close="onSheetClose"
  >
    <f7-page-content :placeholder="title">
      <div class="sign-block">
        <sign-canvas
          class="sign-canvas"
          ref="SignCanvas"
          :options="signOptions"
          v-model="signValue"
        />
      </div>
      <f7-toolbar class="sign-toolbar">
        <div class="right tool-btn">
          <f7-button fill sheet-close color="red" class="v-sign-btn">关 闭</f7-button>
        </div>
        <div class="center tool-btn">
          <f7-button fill @click="saveSign(false)" class="v-sign-btn">确 定</f7-button>
        </div>
        <div class="center tool-btn" v-show="showNextBtn">
          <f7-button fill @click="saveSign(true)" sheet-close class="v-sign-btn">下一个</f7-button>
        </div>
        <div class="center tool-btn" v-show="showResignBtn">
          <f7-button fill @click="resignClick()" class="v-sign-btn">重 签</f7-button>
        </div>
        <div class="center tool-btn" v-show="showCameraBtn">
          <f7-button fill @click="cameraClick()" class="v-sign-btn">拍 照</f7-button>
        </div>
        <div class="left tool-btn">
          <f7-button fill @click="clearCanvasClick" color="red" class="v-sign-btn">清 空</f7-button>
        </div>
      </f7-toolbar>
      <f7-actions ref="actionType">
        <f7-actions-group>
          <f7-actions-button @click="openFaceSign">人脸签名</f7-actions-button>
          <f7-actions-button @click="openSign">手写签名</f7-actions-button>
          <f7-actions-button color="red" @click="$f7.sheet.close('.sign-popup')">取消</f7-actions-button>
        </f7-actions-group>
      </f7-actions>
    </f7-page-content>
  </f7-sheet>
</template>

<script>
import SignCanvas from "sign-canvas";
export default {
  components: { SignCanvas },
  props: {
    /** sheet 标题。可选 */
    title: {
      type: String,
      default: "签名",
    },
    /** 重签，清除现有签名，重新签名。必选参数 */
    resignClick: {
      type: Function,
      default: undefined,
    },
    /** 签名成功后的回调函数。必选参数
     * @param {Object} signImage 签名后的图片数据，base64
     */
    saveSignCallback: {
      type: Function,
      default: undefined,
    },
    /** 人脸签名成功后的回调函数。可选参数
     * @param {Object} signImage 签名后的图片数据，base64
     */
    saveFaceSignCallback: {
      type: Function,
      default: () => {},
    },
    /** 保存签名的链接。可选 */
    saveUrl: {
      type: String,
      default: "/api/signature/signature/save",
    },
    /** 获取签名的链接。可选 */
    getUrl: {
      type: String,
      default: "/api/signature/signature/getSignature",
    },
    /** 锁定屏幕的方向，默认正向竖屏；top、right、buttom、left；详情参考util.lockOrientation */
    lockOrientation: {
      type: String,
      default: "top",
    },
    enableSignWaterMarker: {
      type: Boolean,
      default: true
    }, 
    signWaterMarker: {
      type: String,
      default: "/watermarker.png"
    },
    signWaterDensity: {
      type: Number,
      default: 8
    },
    signWaterAlpha: {
      type: Number,
      default: 0.1
    }
  },
  data() {
    return {
      /** 是否人脸签名 */
      isFaceSignTmp: false,
      /** 当前签名数据 */
      signValue: null,
      /** 是否显示“重签”按钮。可选 */
      showResignBtn: true,
      /** 是否显示“下一个”按钮。可选 */
      showNextBtn: true,
      /** 是否显示“拍照”按钮。可选 */
      showCameraBtn:false,
      /** 签名配置 */
      signOptions: {
        /** canvas宽高 [Number] 可选 */
        canvasHeight: document.documentElement.clientHeight - 115,
        /** 高度  [Number] 可选 */
        canvasWidth: document.documentElement.clientWidth - 20,
        /** 画笔大小 */
        writeWidth: 13,
        /** 签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框 */
        isSign: true,
        /** 是否显示边框 [可选] */
        isShowBorder: false,
        /** 背景色 */
        bgColor: "rgba(0, 0, 0, 0)",
      },
      /** 用户自定义参数临时变量 */
      userOptions: null,
      signType: null,
      rangeTolerance: 0.32,
      reTolerance: 0.52
    };
  },
  created() {
    var docEl = document.documentElement;
    var signOptions = this.signOptions;
    signOptions.canvasHeight = docEl.clientHeight;
    signOptions.canvasWidth = docEl.clientWidth;
    if (docEl.clientHeight < docEl.clientWidth) {
      signOptions.canvasHeight = docEl.clientWidth;
      signOptions.canvasWidth = docEl.clientHeight;
    }
    signOptions.canvasHeight -= 115; //减按钮高度
    signOptions.canvasWidth -= 20; //减左右边距
  },
  methods: {
    /**
     * 弹出签名对话框
     * @param {Object} options 配置参数
     */
    openAction(options) {
      if (this.isPlus()) {
        var showActionBtn = false;
        if (options) {
          showActionBtn = options.showActionBtn == true;
        }
        if (showActionBtn) {
          this.$refs.actionType.f7Actions.open();
          return;
        }
      }

      this.openSign(options);
    },
    /**
     * 人脸签名
     * @param {Object} options 配置参数
     */
    openFaceSign(options) {
      var self = this;
      var isView = false;
      if (options) {
        isView = options.isView == true;
      }
      if (isView) {
        return;
      }
      self.userOptions = options;

      if (!window.plus) {
        self.isFaceSignTmp = false;
        self.openSignPopup(options);
        return;
      }

      plus.aiFace.detect(
        { type: "search" },
        (re) => {
          self.isFaceSignTmp = true;
          if (!re || re.success != true) {
            self.$f7.dialog.alert(
              re.message || "未匹到人脸信息，请先登录账号并人脸认证"
            );
            return;
          }

          var faceUserId = re.userId;
          if (faceUserId && faceUserId.indexOf("_") != -1) {
            faceUserId = faceUserId.replace(/_/g, "-");
          }
          var postData = { userId: faceUserId };
          var url = self.getUrl;
          self.$f7.request.postJSON(url, postData, (response) => {
            if (!self.$util.isBoolean(response?.success)) {
              var msg = "服务异常";
              if (response?.message) {
                msg = response.message;
              }
              self.$f7.dialog.alert(msg);
              return;
            }

            if (response.success != true) {
              if (self.userId != faceUserId) {
                var msg =
                  "人脸信息与当前登录用户不一致！人脸主人尚未签名，请先登录账号在我的签名处签名后，再试";
                self.$f7.dialog.alert(msg);
                return;
              }

              self.faceingUserId = faceUserId;
              self.openSignPopup(options);
              return;
            }

            if (self.$util.isFunction(self.saveFaceSignCallback)) {
              self.saveFaceSignCallback(response.parameters);
            }
          });
        },
        (e) => {
          if (e && (e.code == 11000 || e.code == 222207 || e.code == 223106)) {
            self.$f7.dialog.alert("未匹到人脸信息，请先登录账号并人脸认证");
          } else {
            self.$f7.dialog.alert(e.message || "刷脸登录失败，请稍后重试");
          }
        }
      );
    },
    /**
     * 普通签名
     * @param {Object} options 参数，可选
     */
    openSign(options) {
      var self = this;
      var isView = false;
      if (options) {
        isView = options.isView == true;
      }
      if (isView) {
        return;
      }

      self.isFaceSignTmp = false;
      self.userOptions = options;
      self.openSignPopup(options);
    },
    /**
     * 打开签名画板
     * @param {Object} options 参数，可选，{
     *                   isView: [Boolean => undefined] 是否为查看模式，当设置为true时，将不会弹出签名画板；
     * }
     */
    openSignPopup(options) {
      var self = this;
      var isView = false;
      if (options) {
        isView = options.isView == true;
      }
      if (isView) {
        return;
      }

      self.userOptions = options;
      self.$refs.SignCanvas.canvasClear();
      self.$f7.sheet.open(".sign-popup");
      setTimeout(() => {
        self.$util.lockOrientation(self.lockOrientation);
      }, 10);
    },
    /**
     * 保存签名
     * @param {Boolean} isNext 是否保存并继续
     */
    saveSign(isNext) {
      var self = this;
      if (!self.userOptions) {
        self.userOptions = {};
      }

      const img = self.$refs.SignCanvas.saveAsImg();
      self.rotateBase64Img(img, -90, async (nimg) => {
        if (self.isCanvasBlank(nimg) == true) {
          self.faceingUserId = undefined;
          return false;
        }
        try {
          await self.addWaterMarker(nimg);
        } catch (e) {
          console.error(e, "addWaterMarker");
        }
        nimg = nimg.toDataURL("image/png");

        // document.body.innerHTML = `<img src="${nimg}">`;

        if (self.faceingUserId) {
          self.$f7.request.postJSON(self.saveUrl, {
            signImg: nimg,
            userId: self.faceingUserId,
          });
          self.faceingUserId = undefined;
        }

        if (self.$util.isFunction(self.saveSignCallback)) {
          self.saveSignCallback(nimg, isNext);
        }
      });

      if (isNext !== true) {
        self.$f7.sheet.close(".sign-popup");
        return;
      }

      if (self.isFaceSignTmp) {
        self.openFaceSign(self.userOptions);
      } else {
        self.openSignPopup(self.userOptions);
      }
    },
    /**
     * 保存签名
     * @param {Boolean} signImg 签名数据，base64，必选参数
     * @param {Boolean} userId 用户Id，可选参数
     */
    doSaveSign(signImg, userId, successCallbackFunc) {
      var self = this;
      if (!signImg) {
        self.$f7.dialog.alert("请签名后，再尝试保存");
        return;
      }

      var postData = { signImg: signImg, userId: userId };
      self.$f7.preloader.show();
      self.$f7.request.postJSON(self.saveUrl, postData, (r) => {
        self.$f7.preloader.hide();
        if (r?.success) {
          if (self.$util.isFunction(successCallbackFunc)) {
            successCallbackFunc();
          }
          self.$f7.dialog.alert("保存成功");
        } else {
          self.$f7.dialog.alert(r?.message || "保存失败，请稍后再试");
        }
      });
    },
    isShowReSignBtn: function (isShow) {
      this.showResignBtn = isShow;
    },
    isShowNextBtn: function (isShow) {
      this.showNextBtn = isShow;
    },
    /** 清空画布 */
    clearCanvasClick() {
      this.$refs.SignCanvas.canvasClear();
    },
    /** 图片旋转
     * @param {*} src 图片
     * @param {*} edg 旋转角度，必须是90的倍数
     * @param {Function} callbackFunc 回调函数
     */
    rotateBase64Img(src, edg, callbackFunc) {
      var canvas = document.createElement("canvas");
      var ctx = canvas.getContext("2d");

      var imgW; //图片宽度
      var imgH; //图片高度
      var size; //canvas初始大小

      if (edg % 90 != 0) {
        console.error("旋转角度必须是90的倍数!");
        throw "旋转角度必须是90的倍数!";
      }
      edg < 0 && (edg = (edg % 360) + 360);
      const quadrant = (edg / 90) % 4; //旋转象限
      const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 }; //裁剪坐标

      var image = new Image();
      image.crossOrigin = "anonymous";
      image.src = src;

      var self = this;
      image.onload = () => {
        imgW = image.width;
        imgH = image.height;
        size = imgW > imgH ? imgW : imgH;

        canvas.width = size * 2;
        canvas.height = size * 2;
        switch (quadrant) {
          case 0:
            cutCoor.sx = size;
            cutCoor.sy = size;
            cutCoor.ex = size + imgW;
            cutCoor.ey = size + imgH;
            break;
          case 1:
            cutCoor.sx = size - imgH;
            cutCoor.sy = size;
            cutCoor.ex = size;
            cutCoor.ey = size + imgW;
            break;
          case 2:
            cutCoor.sx = size - imgW;
            cutCoor.sy = size - imgH;
            cutCoor.ex = size;
            cutCoor.ey = size;
            break;
          case 3:
            cutCoor.sx = size;
            cutCoor.sy = size - imgW;
            cutCoor.ex = size + imgH;
            cutCoor.ey = size + imgW;
            break;
        }

        ctx.translate(size, size);
        ctx.rotate((edg * Math.PI) / 180);
        ctx.drawImage(image, 0, 0);

        var imgData = ctx.getImageData(
          cutCoor.sx,
          cutCoor.sy,
          cutCoor.ex,
          cutCoor.ey
        );
        if (quadrant % 2 == 0) {
          canvas.width = imgW;
          canvas.height = imgH;
        } else {
          canvas.width = imgH;
          canvas.height = imgW;
        }
        ctx.putImageData(imgData, 0, 0);

        self.$util.isFunction(callbackFunc) && callbackFunc(canvas);
      };
    },
    /** 判断画布是否存在内容
     * @param {*} canvas 画布
     */
    isCanvasBlank(canvas) {
      let blank = document.createElement("canvas"); //系统获取一个空canvas对象
      blank.width = canvas.width;
      blank.height = canvas.height;
      return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空
    },
    /** 添加水印
     * @param {*} canvas 画布
     */
    async addWaterMarker(canvas) {
      var self = this;
      const config = self.$framework.config;
      // if (config.setting.enableSignWaterMarker != true) {
      //   return;
      // }
      if (!this.enableSignWaterMarker) return;

      // const marker = config.setting.signWaterMarker;
      // let marker = "/static/img" + this.signWaterMarker;
      let marker = "";
      marker = marker.replace(/\/\//g, "/");
      // let density = config.setting.signWaterDensity;
      let density = this.signWaterDensity;
      // let alpha = config.setting.signWaterAlpha;
      let alpha = this.signWaterAlpha;
      if (!marker) {
        return;
      }

      var cans = canvas.getContext("2d");
      cans.textAlign = "left";
      cans.textBaseline = "Middle";

      if (marker.startsWith("/static/")) {
        const img = await this.loadImage(marker);
        if (!img) {
          return;
        }

        density = density || 6;
        alpha = alpha || 0.1;
        let fwidth = canvas.width / density;
        let fheight = canvas.height / density;
        for (
          let hh = fheight / (density / 2);
          hh < canvas.height;
          hh += fheight
        ) {
          for (
            let ww = fwidth / (density / 2);
            ww < canvas.width;
            ww += fwidth
          ) {
            cans.globalAlpha = 0.1;
            cans.drawImage(img, ww, hh, 30, 30);
          }
        }
        return;
      }

      density = density || 4;
      alpha = alpha || 0.8;
      let fwidth = canvas.width / density;
      let fheight = canvas.height / density;
      cans.font = "16px Microsoft JhengHei";
      cans.fillStyle = "rgba(180, 180, 180, " + alpha + ")";
      cans.rotate((-20 * Math.PI) / 180);
      for (let hh = -1 * canvas.height; hh < canvas.height * 2; hh += fheight) {
        for (let ww = -1 * canvas.width; ww < canvas.width * 2; ww += fwidth) {
          cans.fillText(marker, ww, hh);
        }
      }
    },
    /** 从指定路径中加载图片
     * @param {*} src 图片路径
     */
    loadImage(src) {
      if (window.plus) {
        src = src.substring(1);
      }
      return new Promise((resolve, reject) => {
        let img = new Image();
        img.crossOrigin = "*";
        img.onload = () => {
          resolve(img);
        };
        img.onerror = () => {
          reject(new Error("图片解析失败"));
        };
        img.src = src;
      });
    },
    isPlus() {
      return window.plus ? true : false;
    },
    onSheetClose() {
      if (this.$util.isPad()) {
        this.$util.unlockOrientation();
      }
    },
    cameraClick(){
      this.$f7.dialog.alert("请在白纸上签名后，拍照上传",()=>{
        if (this.isPlus()) {
          // APP端，调用plus接口拍照上传
          this.getCamera();
        } else {
          // web端，调用浏览器选择文件上传
          this.selectFile();
        }
      });
    },
    selectFile() {
      const inp = document.createElement("input");
      inp.setAttribute("type", "file");
      inp.setAttribute("accept", "image/*");

      inp.addEventListener("change", async e => {
        const img = await this.createImg(e.target.files[0]);
        const canvas = this.handleSignaturePhoto(img);
        const signCanvas = this.$refs.SignCanvas.$el;
        const ctx = signCanvas.getContext("2d");
        this.clearCanvasClick();
        ctx.drawImage(canvas, 0, 0);
        document.body.appendChild(img);
        // img.remove();
        canvas.remove();
      })
      inp.click();
    },
    createImg(file) {
      const img = document.createElement("img");
      const reader = this.isPlus() ? new plus.io.FileReader() : new FileReader();
      reader.readAsDataURL(file);
      return new Promise(resolve => {
        reader.onloadend = (e) => {
          console.log("file.size", file.size);
          this.rangeTolerance = 0.32 - Math.floor(file.size / 10000) * 0.01;
          this.reTolerance = 0.52 - Math.floor(file.size / 50000) * 0.02;
          this.rangeTolerance < 0 && (this.rangeTolerance = 0);
          this.reTolerance < 0 && (this.reTolerance = 0);
          img.src = e.target.result;
          img.addEventListener("load", () => {
            resolve(img);
          })
        }
      })
    },
    handleSignaturePhoto(img) {
      let canvasWidth,canvasHeight;
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      let devicePixelRatio = 1;

      ctx.imageSmoothingEnabled = true;

      canvas.width = canvasWidth = img.width;
      canvas.height = canvasHeight = img.height;

      // 将图片画到画布上
      ctx.drawImage(img, 0, 0);

      // 读取画布数据
      const originalImageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
      // 查找签名范围
      let space = 5;
      let { sX, sY, eX, eY } = this.getAutographRange(originalImageData.data, canvasWidth, canvasHeight);
      
      sX-=space;
      sY-=space;
      eX+=space;
      eY+=space;
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      
      canvas.width = canvasWidth = this.$refs.SignCanvas.$el.width;
      canvas.height = canvasHeight = this.$refs.SignCanvas.$el.height;
      ctx.filter = `blur(1px) contrast(2)`;

      let wWidth = eX - sX;
      let wHeight = eY - sY;
      if (wWidth <= canvasWidth / 4) {
        devicePixelRatio = window.devicePixelRatio || 2;
      }
      if (wWidth > canvasWidth) {
        devicePixelRatio = canvasWidth / wWidth * 0.8;
      }
      ctx.scale(devicePixelRatio, devicePixelRatio);
      ctx.drawImage(img, sX, sY, wWidth, wHeight, 0, 0, wWidth, wHeight);

      const cropImageData = ctx.getImageData(0, 0, wWidth * devicePixelRatio + 1, wHeight * devicePixelRatio + 1);
      const cropHandleData = this.handleBg(cropImageData.data);
      ctx.putImageData(new ImageData(new Uint8ClampedArray(cropHandleData), wWidth * devicePixelRatio + 1, wHeight * devicePixelRatio + 1), 0, 0);

      let degree = 0;
      let tranformX = canvasWidth / 2;
      let tranformY = canvasHeight / 2;
      if (wWidth > wHeight) {
        degree = 90;
        tranformX += wHeight / 2 * devicePixelRatio;
        tranformY -= wWidth / 2 * devicePixelRatio;
      } else {
        tranformX -= wWidth / 2 * devicePixelRatio;
        tranformY -= wHeight / 2 * devicePixelRatio;
      }
      const rCanvas = document.createElement("canvas");
      rCanvas.width = canvasWidth;
      rCanvas.height = canvasHeight;
      const rCtx = rCanvas.getContext("2d");
      // rCtx.filter = `blur(1px) contrast(2)`;
      rCtx.translate(tranformX, tranformY);
      rCtx.rotate(degree * Math.PI / 180);
      rCtx.drawImage(canvas, 0, 0);

      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      canvas.remove();
      return rCanvas;
    },
    getAutographRange(imageData, w, h) {
      let result = { sX: 0, sY: 0, eX: 0, eY: 0 }; // 签名范围
      // 暂存y轴最大值和最小值，在扫描x轴时获取到x最大和最小时的y分别赋值给这两个，减少y轴扫描范围
      let sY = 0, eY = 0;
      let tolerance = this.rangeTolerance;
      // 拷贝图片像素点数据
      const copyImageData = Array.from(imageData);
      // 查找x轴最左边左边点
      // outer_block用于为内部打断外层循环使用
      outer_block:for (let i = 0; i < w; i ++) {
        for (let j = 0; j < h; j++) {
          let point = (i + w * j) * 4;
          if (this.contrastColor(copyImageData.slice(point, point + 3), tolerance)) {
            result.sX = i;
            sY = eY = j;
            break outer_block;
          }
        }
      }
      // 查找x轴最右边
      outer_block:for(let i = w - 1; i >= 0; i--) {
        for (let j = 0; j < h; j++) {
          let point = (i + w * j) * 4;
          if (this.contrastColor(copyImageData.slice(point, point + 3), tolerance)) {
            result.eX = i;
            j < sY && (sY = j);
            j > eY && (eY = j);
            break outer_block;
          }
        }
      }
      let { sX, eX } = result;
      // 查找y轴最上边
      outer_block:for (let i = 0; i < sY; i++) {
        for (let j = sX; j < eX; j++) {
          let point = (j + w * i) * 4;
          if (this.contrastColor(copyImageData.slice(point, point + 3), tolerance)) {
            sY = i;
            break outer_block;
          }
        }
      }
      // 查找y轴最下边
      outer_block:for (let i = h - 1; i > eY; i--) {
        for (let j = sX; j <= eX; j++) {
          let point = (j + w * i) * 4;
          if (this.contrastColor(copyImageData.slice(point, point + 3), tolerance)) {
            eY = i;
            break outer_block;
          }
        }
      }

      result.sY = sY;
      result.eY = eY;

      return result;
    },
    handleBg(imageData) {
      const copyImageData = Array.from(imageData);
      for (let i = 0; i < copyImageData.length; i+=4) {
        !this.contrastColor(copyImageData.slice(i, i + 3), this.reTolerance) && copyImageData.splice(i + 3, 1, 0);
      }
      return copyImageData;
    },
    getCamera() {
      let that = this;
      let cmr = plus.camera.getCamera();
      cmr.captureImage(function(p){
        plus.io.resolveLocalFileSystemURL(p, function(entry){
          //文件entry
          entry.file(async file => {
            const img = await that.createImg(file);
            const canvas = that.handleSignaturePhoto(img);
            const signCanvas = that.$refs.SignCanvas.$el;
            const ctx = signCanvas.getContext("2d");
            that.clearCanvasClick();
            ctx.drawImage(canvas, 0, 0);

            img.remove();
            canvas.remove();
          })
        }, function(e){
          //'读取拍照文件错误：'+e.message
        });
      }, function(e){
        //'失败：'+e.message
      }, {filename:'_doc/camera/',index:1});
    },
    contrastColor(color1, tolerance = 0.52, color2 = [0,0,0]) {
      let [r1, g1, b1] = color1;
      let [r2, g2, b2] = color2;
      let r3 = (r1 - r2) / 256;
      let g3 = (g1 - g2) / 256;
      let b3 = (b1 - b2) / 256;
      let diff = Math.sqrt(r3 * r3 + g3 * g3 + b3 * b3);

      if (diff <= tolerance) {
        return true;
      } else {
        return false;
      }
    }
  },
};
</script>

<style lang="less" scoped>
@deep: ~">>>";
@{deep}.toolbar-inner {
  padding: 0px !important;
}
.sign-toolbar .tool-btn {
  width: 20%;
}
.sign-popup .sign-block {
  align-items: center;
  display: flex;
  justify-content: center;
  border: 1px solid rgb(238, 238, 238);
  border-radius: 10px;
  margin: 10px;
  height: auto;
}

.sign-popup .sign-toolbar {
  height: 95px;
}

.sign-popup .v-sign-btn {
  -webkit-transform: rotate(90deg) translate(0, 120%);
  transform: rotate(90deg) translate(0, 120%);
  -webkit-transform-origin: 80% 100%;
  transform-origin: 80% 100%;
  padding: 0;
  margin: 0 !important;
}

.sign-popup {
  & + ::v-deep .van-overlay, & ~ ::v-deep .van-dialog {
    z-index: 99998 !important;
  }
}
</style>
